#include "webserver.h"
#include <sys/epoll.h>

WebServer::WebServer(
        int port, int trigMode, int timeoutMS, bool optLinger,
        int sqlPort, const char* sqlUser, const  char* sqlPwd,
        const char* dbName, int connPoolNum, int threadNum,
        bool openLog, int logLevel, int logQueSize):
        m_port(port), m_timeout(timeoutMS), m_openLinger(optLinger), m_isClose(false), 
        m_timer(new HeapTimer()), m_threadpool(new ThreadPool(threadNum)), m_Epoller(new Epoller()) {
    m_srcDir = getcwd(nullptr, 256);
    assert(m_srcDir);
    strncat(m_srcDir, "/resources/", 16); // 在结尾增加字符串
    HttpConn::userCount = 0;
    HttpConn::srcDir = m_srcDir;
    // 初始化数据库
    SqlConnPool::Instance()->Init("localhost", sqlPort, sqlUser, sqlPwd, dbName, connPoolNum);
    // 设置触发模式
    InitEventMode(trigMode);

    // 初始化socket
    if (!InitSocket()) {
        m_isClose = true;
    }

    // 初始化log
    printf("%d", logLevel);
    if (openLog) {
        Log::Instance()->Init(logLevel, "./log", ".log", logQueSize);
        if (!m_isClose) {
            LOG_INFO("========== Server init ==========");
            LOG_INFO("Port:%d, OpenLinger: %s", m_port, optLinger? "true":"false");
            LOG_INFO("Listen Mode: %s, OpenConn Mode: %s",
                            (m_listenEvent & EPOLLET ? "ET": "LT"),
                            (m_connEvent & EPOLLET ? "ET": "LT"));
            LOG_INFO("LogSys level: %d", logLevel);
            LOG_INFO("srcDir: %s", HttpConn::srcDir);
            LOG_INFO("SqlConnPool num: %d, ThreadPool num: %d", connPoolNum, threadNum);
        }
    }
}

WebServer::~WebServer() {
    close(m_listenFd);
    m_isClose = true;
    free(m_srcDir);
    SqlConnPool::Instance()->ClosePool();
}

void WebServer::Start() {
    int timeMS = -1; // epoll_wait阻塞
    if (!m_isClose) {
        LOG_INFO("========== Server start ==========");
    }
    while (!m_isClose) {
        if (m_timeout > 0) {
            // 如果服务器超时时间>0,那么获取下一个定时器的时间作为epoll_wait的等待时间,从而避免阻塞时定时器时间到
            timeMS = m_timer->GetNextTick();
        }
        int eventCnt = m_Epoller->Wait(timeMS);
        // 处理事件
        for (int i = 0; i < eventCnt; ++i) {
            int fd = m_Epoller->GetEventFd(i);
            uint32_t events = m_Epoller->GetEvents(i);
            // 监听事件，有新用户连接
            if (fd == m_listenFd) {
                DealListen();
            } else if (events | (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
                assert(m_users.count(fd) > 0);
                CloseConn(&m_users[fd]);
            } else if (events | EPOLLIN) {
                assert(m_users.count(fd) > 0);
                DealRead(&m_users[fd]);
            } else if (events | EPOLLOUT) {
                assert(m_users.count(fd) > 0);
                DealWrite(&m_users[fd]);
            } else {
                LOG_ERROR("unexpected event");
            }
        }
    }
}

void WebServer::DealWrite(HttpConn* client) {
    assert(client);
    ExtentTime(client);
    m_threadpool->AddTask(std::bind(&WebServer::OnWrite, this, client));
}

void WebServer::OnWrite(HttpConn* client) {
    assert(client);
    int writeerrno = 0;
    int ret = -1;
    ret = client->Write(&writeerrno);
    // 已经写完
    if (client->ToWriteBytes()) {
        if (client->IsKeepAlive()) {
            OnProcess(client);
            return;            
        }
    } else if (ret < 0) {
        if (writeerrno == EAGAIN) {
            // 未写完的话，再激活EPOLLOUT
            m_Epoller->ModFd(client->GetFd(), EPOLLOUT);
            return;
        }
    }
    CloseConn(client);
}

void WebServer::DealRead(HttpConn* client) {
    assert(client);
    ExtentTime(client);
    m_threadpool->AddTask(std::bind(&WebServer::OnRead, this, client)); // std::bind绑定函数为std::funtion，类中使用第二个参数为this
}

void WebServer::ExtentTime(HttpConn* client) {
    assert(client);
    if (m_timeout > 0) {
        m_timer->Adjust(client->GetFd(), m_timeout);
    }
}

void WebServer::OnRead(HttpConn* client) {
    assert(client);
    int ret = -1;
    int readerrno = 0;
    ret = client->Read(&readerrno);
    if (ret < 0 && readerrno != EAGAIN) {
        CloseConn(client);
        return;
    }
    OnProcess(client);
}

void WebServer::OnProcess(HttpConn* client) {
    if (client->Process()) {
        // 读完了，关注写操作
        m_Epoller->ModFd(client->GetFd(), EPOLLOUT);
    } else {
        // 未读完，关注读操作
        m_Epoller->ModFd(client->GetFd(), EPOLLIN);
    }
}

void WebServer::DealListen() {
    // 当是ET模式时，要循环执行，直到全部事件处理完，LT模式只用执行一次，下次外部循环时epoll_wait又会触发
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    do {
        int fd = accept(m_listenFd, (struct sockaddr *)&addr, &len);
        if (fd < 0) {
            return;
        } else if (HttpConn::userCount >= MAX_FD) {
            // 已有用户数 >= 最大描述符数
            SendError(fd, "Server busy");
            LOG_WARN("Clients is full");
            return;
        }
        AddClient(fd, addr);
    } while(m_listenEvent & EPOLLET);
}

void WebServer::SendError(int fd, const char* info) {
    assert(fd > 0); 
    int ret = send(fd, info, strlen(info), 0);
    if (ret < 0) {
        LOG_WARN("send error to client[%d] error", fd);
    }
    close(fd);
}

void WebServer::CloseConn(HttpConn* client) {
    assert(client != nullptr);
    LOG_INFO("client[%d] quit", client->GetFd());
    m_Epoller->DelFd(client->GetFd());
    client->Close();
}

void WebServer::AddClient(int fd, sockaddr_in addr) {
    assert(fd > 0);
    m_users[fd].Init(fd, addr);
    if (m_timeout > 0) {
        m_timer->Add(fd, m_timeout, std::bind(&WebServer::CloseConn, this, &m_users[fd]));
    }
    m_Epoller->AddFd(fd, EPOLLIN | m_connEvent);
    SetFdNonblock(fd);
    LOG_INFO("client[%d] in", m_users[fd].GetFd());
}

bool WebServer::InitSocket() {
    int ret;
    struct sockaddr_in addr;
    if (m_port > 65535 || m_port < 1024) {
        LOG_ERROR("Port:%d error", m_port);
        return false;
    }
    // 设置地址族、ip地址、端口
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(m_port);
    struct linger optLinger = {0};

    // 设置优雅关闭
    if (m_openLinger) {
        optLinger.l_onoff = 1;
        optLinger.l_linger = 1;
    }

    // 创建监听socket，地址族，套接字类型，协议类型（为0自动选择）
    m_listenFd = socket(AF_INET, SOCK_STREAM, 0);
    if (m_listenFd < 0) {
        LOG_ERROR("create listen socket error");
        return false;
    }

    // 设置优雅关闭
    ret = setsockopt(m_listenFd, SOL_SOCKET, SO_LINGER, &optLinger, sizeof(optLinger));
    if (ret < 0) {
        close(m_listenFd);
        LOG_ERROR("Init linger error");
    }

    // 设置端口复用
    int optval = 1;
    ret = setsockopt(m_listenFd, SOL_SOCKET, SO_REUSEADDR, (const void*)(&optval), sizeof(int));
    if (ret == -1) {
        LOG_ERROR("set socket setsockopt error");
        close(m_listenFd);
        return false;
    }

    // 绑定地址
    ret = bind(m_listenFd, (struct sockaddr*)&addr, sizeof(addr));
    if (ret < 0) {
        LOG_ERROR("bind socket error");
        close(m_listenFd);
        return false;
    }
    // 第二个参数是请求队列的长度
    ret = listen(m_listenFd, 6);
    if (ret < 0) {
        LOG_ERROR("listen socket error");
        close(m_listenFd);
        return false;
    }
    ret = m_Epoller->AddFd(m_listenFd, m_listenEvent | EPOLLIN);
    if(ret == 0) {
        LOG_ERROR("Add listen error!");
        close(m_listenFd);
        return false;
    }
    SetFdNonblock(m_listenFd);
    LOG_INFO("Server port:%d", m_port);
    return true;
}

// EPOLLRDHUP：对面关闭连接时会触发EPOLLIN事件
// EPOLLONESHOT：一个事件被触发时，该事件将禁用，直到显式重新注册，防止线程处理后事件仍活跃导致其他线程尝试处理
// EPOLLET：边缘触发，状态改变时才触发一次
void WebServer::InitEventMode(int trigMode) {
    m_listenEvent = EPOLLRDHUP;
    m_connEvent = EPOLLONESHOT | EPOLLRDHUP;
    switch(trigMode) {
        case 0:
            break;
        case 1:
            m_connEvent |= EPOLLET;
            break;
        case 2:
            m_listenEvent |= EPOLLET;
        case 3:
            m_connEvent |= EPOLLET;
            m_listenEvent |= EPOLLET;
            break;
        default:
            m_connEvent |= EPOLLET;
            m_listenEvent |= EPOLLET;
            break;
    }
    // 设置httpconn的模式
    HttpConn::isET = (m_connEvent & EPOLLET);
}

int WebServer::SetFdNonblock(int fd) {
    assert(fd > 0);
    // 设置非阻塞，先获取原有，再加上
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0) | O_NONBLOCK);
}